package simmar.xcs;

import simmar.xcs.XCSKnowledge;
import simmar.utilities.Logger;
import simmar.xcs.XCSConfig.ObservationType;
import ch.idsia.benchmark.mario.engine.GeneralizerLevelScene;
import ch.idsia.benchmark.mario.engine.sprites.Sprite;
import ch.idsia.benchmark.mario.environments.Environment;

public class XCSObservation
{
    // public static final int CANNON_MUZZLE = -82;
    // public static final int CANNON_TRUNK = -80;
    // public static final int COIN_ANIM = 2;
    // public static final int BREAKABLE_BRICK = -20;
    // public static final int UNBREAKABLE_BRICK = -22; //a rock with animated
    // question mark
    // public static final int BRICK = -24; //a rock with animated question mark
    // public static final int FLOWER_POT = -90;
    // public static final int BORDER_CANNOT_PASS_THROUGH = -60;
    // public static final int BORDER_HILL = -62;
    // public static final int FLOWER_POT_OR_CANNON = -85;

    // public static final int KIND_NONE = 0;
    // public static final int KIND_MARIO = -31;
    // public static final int KIND_GOOMBA = 80;
    // public static final int KIND_GOOMBA_WINGED = 95;
    // public static final int KIND_RED_KOOPA = 82;
    // public static final int KIND_RED_KOOPA_WINGED = 97;
    // public static final int KIND_GREEN_KOOPA = 81;
    // public static final int KIND_GREEN_KOOPA_WINGED = 96;
    // public static final int KIND_BULLET_BILL = 84;
    // public static final int KIND_SPIKY = 93;
    // public static final int KIND_SPIKY_WINGED = 99;
    // // public static final int KIND_ENEMY_FLOWER = 11;
    // public static final int KIND_ENEMY_FLOWER = 91;
    // public static final int KIND_WAVE_GOOMBA = 98; // TODO: !H!: same
    // public static final int KIND_SHELL = 13;
    // public static final int KIND_MUSHROOM = 2;
    // public static final int KIND_GREEN_MUSHROOM = 9;
    // public static final int KIND_PRINCESS = 49;
    // public static final int KIND_FIRE_FLOWER = 3;
    // public static final int KIND_PARTICLE = 21;
    // public static final int KIND_SPARCLE = 22;
    // public static final int KIND_COIN_ANIM = 1;
    // public static final int KIND_FIREBALL = 25;
    //
    // public static final int KIND_UNDEF = -42;

    // public static void reset()
    // {
    // receptiveFieldWidth = 21;
    // receptiveFieldHeight = 21;
    // marioEgoRow = 10;
    // marioEgoCol = 10;
    //
    // levelCells = null;
    // enemyCells = null;
    // mergedCells = null;
    //
    // zLevelScene = 1;
    // zLevelEnemies = 2;
    //
    // marioState = null;
    //
    // jumpInterval = 0;
    // canJump = 1;
    // }
    //
    // public static void setObservationDetails(final int rfWidth, final int
    // rfHeight, final int egoRow, final int egoCol)
    // {
    // receptiveFieldWidth = rfWidth;
    // receptiveFieldHeight = rfHeight;
    //
    // marioEgoRow = egoRow;
    // marioEgoCol = egoCol;
    // }
    //
    // public static void update(Environment environment, Action previousAction)
    // {
    // levelCells = environment.getLevelSceneObservationZ(zLevelScene);
    // enemyCells = environment.getEnemiesObservationZ(zLevelEnemies);
    // mergedCells = environment.getMergedObservationZZ(zLevelScene,
    // zLevelScene);
    // marioState = environment.getMarioState();
    //
    // if (previousAction == Action.RIGHT_JUMP_SPEED)
    // {
    // if (jumpInterval > 16)
    // {
    // jumpInterval = 0;
    // canJump = 0;
    // }
    // else
    // {
    // canJump = 1;
    // jumpInterval++;
    // }
    // }
    // }
    //
    // public static int mergedCellIsClear(int rowOffset, int colOffset)
    // {
    // switch (mergedCells[marioEgoRow + rowOffset][marioEgoCol + colOffset])
    // {
    // case 0:
    // case GeneralizerLevelScene.COIN_ANIM:
    // case GeneralizerLevelScene.BORDER_HILL:
    // case Sprite.KIND_FIREBALL:
    // return 1;
    // default:
    // return 0;
    // }
    // }
    //
    // public static int levelCellIsClear(int rowOffset, int colOffset)
    // {
    // switch (levelCells[marioEgoRow + rowOffset][marioEgoCol + colOffset])
    // {
    // case 0:
    // case GeneralizerLevelScene.COIN_ANIM:
    // case GeneralizerLevelScene.BORDER_HILL:
    // case Sprite.KIND_FIREBALL:
    // return 1;
    // default:
    // return 0;
    // }
    // }
    //
    // public static int enemyCellIsClear(int rowOffset, int colOffset)
    // {
    // int cellValue = enemyCells[marioEgoRow + rowOffset][marioEgoCol +
    // colOffset];
    //
    // return cellValue > 0 ? 0 : 1;
    // }
    //
    // public static int isMarioBig()
    // {
    // return marioState[1] > 0 ? 1 : 0;
    // }
    //
    // public static int canMarioJump(Action previousAction)
    // {
    // return canJump;
    //
    // // boolean canJump = false
    // //
    // // else
    // // {
    // // trueJumpCounter = 0;
    // // return false;
    // // }
    // //
    // // if (trueJumpCounter > 16)
    // // {
    // // trueJumpCounter = 0;
    // // return false;
    // // }
    // // return true;
    //
    // // if (previousAction == Action.RIGHT_JUMP_SPEED)
    // // return 1;
    // // else
    // // return 0;
    // }

    /**
     * Get if right is clear
     * 
     * @param knowledge
     *            - memorized knowledge of the agent
     * @param environment
     *            - the current environment
     * @return true if adjacent cell to the right is clear and safe
     */
    public static double getBinaryClearRight(XCSKnowledge knowledge, Environment environment)
    {
        int[] center = environment.getMarioEgoPos();
        byte grid[][] = environment.getMergedObservationZZ(1, 2);

        // TODO: Account for large mario's size

        switch (grid[center[0]][center[1] + 1])
        {
        case 0:
        case GeneralizerLevelScene.COIN_ANIM:
        case GeneralizerLevelScene.BORDER_HILL:
            return 1.0;
        default:
            return 0.0;
        }
    }

    /**
     * Get if left is clear
     * 
     * @param knowledge
     *            - memorized knowledge of the agent
     * @param environment
     *            - the current environment
     * @return true if adjacent cell to the left is clear and safe
     */
    public static double getBinaryClearLeft(XCSKnowledge knowledge, Environment environment)
    {
        int[] center = environment.getMarioEgoPos();
        byte grid[][] = environment.getMergedObservationZZ(1, 2);

        // TODO: Account for large mario's size

        switch (grid[center[0]][center[1] - 1])
        {
        case 0:
        case GeneralizerLevelScene.COIN_ANIM:
        case GeneralizerLevelScene.BORDER_HILL:
            return 1.0;
        default:
            return 0.0;
        }
    }

    /**
     * Get if up is clear
     * 
     * @param knowledge
     *            - memorized knowledge of the agent
     * @param environment
     *            - the current environment
     * @return true if adjacent cell above is clear and safe
     */
    public static double getBinaryClearAbove(XCSKnowledge knowledge, Environment environment)
    {
        int[] center = environment.getMarioEgoPos();
        byte grid[][] = environment.getMergedObservationZZ(1, 2);

        // TODO: Check enemy type for enemies that can be defeated from below

        switch (grid[center[0] - 1][center[1]])
        {
        case 0:
        case GeneralizerLevelScene.COIN_ANIM:
        case GeneralizerLevelScene.BORDER_HILL:
            return 1.0;
        default:
            return 0.0;
        }
    }

    /**
     * Get if down is clear
     * 
     * @param knowledge
     *            - memorized knowledge of the agent
     * @param environment
     *            - the current environment
     * @return true if adjacent cell below is clear and safe
     */
    public static double getBinaryClearBelow(XCSKnowledge knowledge, Environment environment)
    {
        int[] center = environment.getMarioEgoPos();
        byte grid[][] = environment.getMergedObservationZZ(1, 2);

        // TODO: Check enemy type for enemies that can be defeated from above

        switch (grid[center[0] + 1][center[1]])
        {
        case 0:
        case GeneralizerLevelScene.COIN_ANIM:
        case GeneralizerLevelScene.BORDER_HILL:
            return 1.0;
        default:
            return 0.0;
        }
    }

    /**
     * Get if Mario can ascend by jumping
     * 
     * @param knowledge
     * @param environment
     * @return true if maximum jump height hasn't been reached yet or Mario can
     *         jump
     */
    public static double getBinaryCanAscend(XCSKnowledge knowledge, Environment environment)
    {
        if (knowledge.marioJumping)
        {
            if (knowledge.marioJumpCounter < XCSKnowledge.marioJumpLimit)
            {
                return 1.0;
            } else
            {
                return 0.0;
            }
        } else
        {
            if (knowledge.marioCanJump)
            {
                return 1.0;
            } else
            {
                return 0.0;
            }
        }
    }

    /**
     * Get if there's an enemy to the right of Mario within 3 cells
     * 
     * @param knowledge
     * @param environment
     * @return true if there's an enemy
     */
    public static double getBinaryEnemyAhead(XCSKnowledge knowledge, Environment environment)
    {
        int[] center = environment.getMarioEgoPos();
        byte grid[][] = environment.getMergedObservationZZ(1, 2);

        for (int i = 1; i <= 3; i++)
        {
            switch (grid[center[0]][center[1] + i])
            {
            case 1:
                return 1.0;
            default:
            }
        }

        return 0.0;
    }

    /**
     * Get if there's an obstacle to the right of Mario within 3 cells
     * 
     * @param knowledge
     * @param environment
     * @return true if there's an obstacle
     */
    public static double getBinaryObstacleAhead(XCSKnowledge knowledge, Environment environment)
    {
        int[] center = environment.getMarioEgoPos();
        byte grid[][] = environment.getLevelSceneObservationZ(1);

        for (int i = 1; i <=3; i++)
        {
            boolean clear = false;

            switch (grid[center[0]][center[1] + i])
            {
            case 0:
            case GeneralizerLevelScene.COIN_ANIM:
            case GeneralizerLevelScene.BORDER_HILL:
            case 1:
                clear = true;
                break;
            default:
                return 1.0;
            }

            if (clear)
            {
                switch (grid[center[0] + 1][center[1] + i])
                {
                case 0:
                case GeneralizerLevelScene.COIN_ANIM:
                case GeneralizerLevelScene.BORDER_HILL:
                case 1:
                    return 0.0;
                default:
                }
            }
        }

        return 0.0;
    }

    /**
     * Get if there's a pit to the right of Mario within 5 cells that can only
     * be passed by jumping
     * 
     * @param knowledge
     * @param environment
     * @return true if there's a pit
     */
    public static double getBinaryPitAhead(XCSKnowledge knowledge, Environment environment)
    {
        int[] center = environment.getMarioEgoPos();
        byte grid[][] = environment.getMergedObservationZZ(1, 2);

        // for (int i = 1; i<=5; i++) {
        // for (int j = 1; (center[0]+j)<grid.length; j++) {
        // switch (grid[ center[0]+j ][ center[1]+i ]) {
        // case 0:
        // case GeneralizerLevelScene.COIN_ANIM:
        // case GeneralizerLevelScene.BORDER_HILL:
        // return 1.0;
        // default:
        // }
        // }
        // }
        //
        // return 0.0;

        double pitAhead = 0.0;
        
        for (int i = 2; i <= 6; i++)
        {
            for (int j = 1; (center[0] + j) < grid.length; j++)
            {
                switch (grid[center[0] + j][center[1] + i])
                {
                case 0:
                case GeneralizerLevelScene.COIN_ANIM:
                case GeneralizerLevelScene.BORDER_HILL:
                    pitAhead = 1.0;
                default:
                    pitAhead = 0.0;
                }
            }
        }

        return pitAhead;
    }

    /**
     * Get if there's a pit or dangerous enemy below mario
     * 
     * @param knowledge
     * @param environment
     * @return true if there's a danger
     */
    public static double getBinaryDangerBelow(XCSKnowledge knowledge, Environment environment)
    {
        int[] center = environment.getMarioEgoPos();
        byte grid[][] = environment.getMergedObservationZZ(1, 0);

        for (int i = 1; (center[0] + i) < grid.length; i++)
        {
            switch (grid[center[0] + i][center[1]])
            {
            case 0:
            case GeneralizerLevelScene.COIN_ANIM:
            case GeneralizerLevelScene.BORDER_HILL:
                break;
            case Sprite.KIND_ENEMY_FLOWER:
            case Sprite.KIND_SPIKY:
            case Sprite.KIND_SPIKY_WINGED:
                return 1.0;
            default:
                return 0.0;
            }
        }

        return 1.0;
    }

    /**
     * Get if there's an enemy lined up for a shot and mario can shoot
     * 
     * @param knowledge
     * @param environment
     * @return true if there's an enemy lined up
     */
    public static double getBinaryShotAimed(XCSKnowledge knowledge, Environment environment)
    {
        if (knowledge.marioCanShoot)
        {
            int[] center = environment.getMarioEgoPos();
            byte grid[][] = environment.getMergedObservationZZ(1, 2);

            if (grid[center[0]][center[1] + 1] == 1)
            {
                return 1.0;
            } else if (grid[center[0]][center[1] + 2] == 1)
            {
                return 1.0;
            } else if (grid[center[0] + 1][center[1] + 1] == 1)
            {
                return 1.0;
            } else if (grid[center[0] + 1][center[1] + 2] == 1)
            {
                return 1.0;
            } else
            {
                return 0.0;
            }
        } else
        {
            return 0.0;
        }
    }

    public static void printNeighborhood(int length, Environment environment, ObservationType type)
    {
        int startIndexX = environment.getMarioEgoPos()[1] - (length / 2);
        int startIndexY = environment.getMarioEgoPos()[0] - (length / 2);
        int endIndexX = startIndexX + length;
        int endIndexY = startIndexY + length;

        byte[][] grid = null;

        if (type == ObservationType.LEVEL)
            grid = environment.getLevelSceneObservationZ(1);

        if (type == ObservationType.ENEMIES)
            grid = environment.getEnemiesObservationZ(2);

        if (type == ObservationType.MERGED)
            grid = environment.getMergedObservationZZ(1, 2);

        String headline = length + "x" + length + " observation grid for " + type.toString();
        Logger.logLine("------------------------------------------------------------------------------------");
        Logger.logLine("------------------------- " + headline + " ------------------------- ");
        Logger.logLine("------------------------------------------------------------------------------------");

        for (int i = startIndexX; i < endIndexX; i++)
        {
            Logger.logMethod();

            for (int j = startIndexY; j < endIndexY; j++)
            {
                if (i == environment.getMarioEgoPos()[1] && j == environment.getMarioEgoPos()[0])
                    System.out.printf("%4s", "M");
                else
                    System.out.printf("%4d", grid[i][j]);
            }

            Logger.log("\n");
        }
    }

    // private static int getEnemyCellValue(int x, int y)
    // {
    // if (x < 0 || x >= enemyCells.length || y < 0 || y >=
    // enemyCells[0].length)
    // return 0;
    //
    // return enemyCells[x][y];
    // }
    //
    // private static int getLevelCellValue(int x, int y)
    // {
    // if (x < 0 || x >= levelCells.length || y < 0 || y >=
    // levelCells[0].length)
    // return 0;
    //
    // return levelCells[x][y];
    // }

    // public int getZoomLevelScene()
    // {
    // return zLevelScene;
    // }
    //
    // public int getZoomLevelEnemies()
    // {
    // return zLevelEnemies;
    // }
}